package com.diy.sigmund.diycommon.test.controller.async;

import com.diy.sigmund.diycommon.entity.CustomUser;
import com.diy.sigmund.diycommon.entity.ResultMsg;
import com.diy.sigmund.diycommon.entity.constant.CommonConstant;
import com.diy.sigmund.diycommon.entity.constant.UserConstant;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisService;
import com.diy.sigmund.diycommon.spring.ContextHolder;
import com.diy.sigmund.diycommon.spring.CustomApplicationContext;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;

/**
 * @author ylm-sigmund
 * @since 2022/7/30 23:16
 */
@RestController
public class WebAsyncTaskTestController {

    @Autowired
    private RedisService redisService;
    @Resource(name = "asyncTestExecutor")
    private AsyncTaskExecutor asyncTestExecutor;
    private static final Logger log = LoggerFactory.getLogger(WebAsyncTaskTestController.class);

    @PostMapping(value = "/webAsyncTask/insertToken", produces = MediaType.APPLICATION_JSON_VALUE)
    public void insertToken() {
        // 不用判断key是否存在，因为经过网关已经刷新了失效时间
        String key = UserConstant.USER_ACCESS_TOKEN + CommonConstant.TEST_TOKEN;
        // 从缓存中获取用户信息
        CustomUser customUser = new CustomUser();
        customUser.setUserId("2022-07-31 00:07:00");
        // if (Objects.isNull(customUser)) {
        //     log.info("请求用户获取失败");
        //     throw new DiyRuntimeException(ResponseEnum.FAILED_TO_REQUEST_USER_ACQUISITION);
        // }
        // 将用户信息设置到上下文
        redisService.set(key, customUser, 3, TimeUnit.DAYS);
    }

    /**
     * <h1>异步sevlet,不阻塞请求线程,增加吞吐量<h1/>
     */
    @PostMapping("/common/webAsyncTask/testTransactional/{timeout1}/{timeout2}")
    public WebAsyncTask<ResultMsg> testTransactional(@PathVariable long timeout1, @PathVariable long timeout2) {
        // new AsyncHandlerInterceptor();
        // new AsyncWebRequestInterceptor();
        // new AsyncExecutionInterceptor();

        // 并不是WebAsyncTask异步任务处理自动拷贝上下文,自动清理上下文;而是使用了TransmittableThreadLocal
        // 出参写入traceId
        // 自定义starter
        // DeferredResultProcessingInterceptor 延迟结果处理接收器
        // lombook
        // 拒绝策略下出参会返回2个对象 {"traceId":"3018303330994400","code":"500","msg":"服务器内部错误，请稍后再试","data":null}{"traceId":"3018303373157400","code":"500","msg":"服务器内部错误，请稍后再试","data":null}
        // 异步拦截器没有traceId,只有最后2条CustomCallableProcessingInterceptor

        // 打印处理线程名
        log.info("主线程-The main Thread name is " + Thread.currentThread().getName());
        // if (new Date().before(DateUtil.parseStrToDate("2023", DateUtil.DATE_FORMAT_YYYY))) {
        //     throw new DiyRuntimeException(ResponseEnum.USER_NAME_ERROR);
        // }
        log.info("主线程-该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        log.info("主线程-当前线程的上下文信息={}", JacksonUtil.toJson(ContextHolder.getInstance().getCustomUser()));
        // 此处的注册放主线程前面和放后面都没有问题,放异步线程中不生效
        // WebAsyncUtils.getAsyncManager(CustomApplicationContext.getHttpServletRequest())
        //         .registerCallableInterceptor(CustomCallableProcessingInterceptor.KEY, new CustomCallableProcessingInterceptor());
        // 此处模拟开启一个异步任务,超时时间为10s
        WebAsyncTask<ResultMsg> task1 = getResultMsgWebAsyncTask(timeout1, timeout2);

        log.info("主线程-task1继续处理其他事情！");

        return task1;
    }

    @PostMapping("/common/webAsyncTask/testTransactional2/{timeout1}/{timeout2}")
    public WebAsyncTask<ResultMsg> testTransactional2(@PathVariable long timeout1, @PathVariable long timeout2) {

        // 打印处理线程名
        log.info("主线程-The main Thread name is " + Thread.currentThread().getName());
        log.info("主线程-该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        log.info("主线程-当前线程的上下文信息={}", JacksonUtil.toJson(ContextHolder.getInstance().getCustomUser()));
        WebAsyncTask<ResultMsg> task1 = getResultMsgWebAsyncTask(timeout1, timeout2);

        log.info("主线程-task1继续处理其他事情！");

        return task1;
    }

    @NotNull
    private WebAsyncTask<ResultMsg> getResultMsgWebAsyncTask(long timeout1, long timeout2) {
        log.info("超时时间配置是{},{}", timeout1, timeout2);
        WebAsyncTask<ResultMsg> task1 = new WebAsyncTask<>(
                // 9000_000L,
                // 30_000L,
                timeout1 * 1000,
                asyncTestExecutor,
                () -> {
                    log.info("进入异步任务-任务1,开始执行");
                    log.info("进入异步任务-任务1-该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                            TransactionSynchronizationManager.isActualTransactionActive(),
                            TransactionSynchronizationManager.getCurrentTransactionName(),
                            TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
                    log.info("进入异步任务-任务1-当前异步线程的上下文信息={}", JacksonUtil.toJson(ContextHolder.getInstance().getCustomUser()));
                    // 任务处理时间5s,不超时
                    Thread.sleep(timeout2 * 1000L);
                    // if (new Date().before(DateUtil.parseStrToDate("2023", DateUtil.DATE_FORMAT_YYYY))) {
                    //     throw new DiyRuntimeException(ResponseEnum.USER_NAME_ERROR);
                    // }
                    CustomApplicationContext.getBean(WebAsyncTaskTestController.class).testTestSqlSelect();
                    log.info("进入异步任务-任务1,结束执行,准备返回前端");
                    return ResultMsg.success("异步任务-任务1顺利执行成功！任何异常都没有抛出！");
                }
        );
        return task1;
    }

    @Async("customAsyncExecutor")
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = 9000, readOnly = false, rollbackFor = {
            Exception.class})
    public void testTestSqlSelect() {
        log.info("事务方法testTestSqlSelect-该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        log.info("事务方法testTestSqlSelect-当前异步线程的上下文信息={}", JacksonUtil.toJson(ContextHolder.getInstance().getCustomUser()));
        // if (new Date().before(DateUtil.parseStrToDate("2023", DateUtil.DATE_FORMAT_YYYY))) {
        //     throw new DiyRuntimeException(ResponseEnum.USER_NAME_ERROR);
        // }
    }

    /**
     * 测试没有发生任何异常的异步任务
     * <br>2022-07-10 14:54:08.610 [diy-user] [192.168.21.1] [WARN ] [http-nio-8002-exec-1] [org.springframework.web.context.request.async.WebAsyncManager] - [NONE][NONE][0][10909125745033280]
     * <br>!!!
     * <br>An Executor is required to handle java.util.concurrent.Callable return values.
     * <br>Please, configure a TaskExecutor in the MVC config under "async support".
     * <br>The SimpleAsyncTaskExecutor currently in use is not suitable under load.
     * <br>-------------------------------
     * <br>Request URI: '/getUserWithNoThing.json'
     * <br>!!!
     */
    @RequestMapping(value = "getUserWithNoThing.json", method = RequestMethod.GET)
    public WebAsyncTask<String> getUserWithNoThing() {
        // 打印处理线程名
        System.err.println("The main Thread name is " + Thread.currentThread().getName());

        // 此处模拟开启一个异步任务,超时时间为10s
        WebAsyncTask<String> task1 = new WebAsyncTask<String>(10 * 1000L, () -> {
            System.err.println("The first Thread name is " + Thread.currentThread().getName());
            // 任务处理时间5s,不超时
            Thread.sleep(5 * 1000L);
            return "任务1顺利执行成功！任何异常都没有抛出！";
        });

        // 任务执行完成时调用该方法
        task1.onCompletion(() -> {
            System.err.println("任务1执行完成啦！");
        });

        System.err.println("task1继续处理其他事情！");
        return task1;
    }

    /**
     * 测试发生error的异步任务
     */
    @RequestMapping(value = "getUserWithError.json", method = RequestMethod.GET)
    public WebAsyncTask<String> getUserWithError() {
        System.err.println("The main Thread name is " + Thread.currentThread().getName());

        // 此处模拟开启一个异步任务
        WebAsyncTask<String> task3 = new WebAsyncTask<String>(10 * 1000L, () -> {
            System.err.println("The second Thread name is " + Thread.currentThread().getName());
            // 此处抛出异常
            int num = 9 / 0;
            System.err.println(num);
            return "";
        });

        // 发生异常时调用该方法
        task3.onError(() -> {
            System.err.println("====================================" + Thread.currentThread().getName()
                    + "==============================");
            System.err.println("任务3发生error啦！");
            return "";
        });
        // 任务执行完成时调用该方法
        task3.onCompletion(() -> {
            System.err.println("任务3执行完成啦！");
        });

        System.err.println("task3继续处理其他事情！");
        return task3;
    }

    /**
     * 测试发生任务超时的异步任务
     * <br>可以试着作为超时配置
     */
    @RequestMapping(value = "getUserWithTimeOut.json", method = RequestMethod.GET)
    public WebAsyncTask<String> getUserWithTimeOut() {
        System.err.println("The main Thread name is " + Thread.currentThread().getName());

        // 此处模拟开启一个异步任务,超时10s
        WebAsyncTask<String> task2 = new WebAsyncTask<String>(10 * 1000L, () -> {
            System.err.println("The second Thread name is " + Thread.currentThread().getName());
            Thread.sleep(20 * 1000L);
            return "任务2执行超时！";
        });

        // 任务超时调用该方法
        task2.onTimeout(() -> {
            System.err.println("====================================" + Thread.currentThread().getName()
                    + "==============================");
            return "任务2发生超时啦！";
        });

        // 任务执行完成时调用该方法
        task2.onCompletion(() -> {
            System.err.println("任务2执行完成啦！");
        });

        System.err.println("task2继续处理其他事情！");
        return task2;
    }

    /**
     * 测试线程池
     * <br>真实响应时间5557ms
     */
    @RequestMapping(value = "getUserWithExecutor.json", method = RequestMethod.GET)
    public WebAsyncTask<String> getUserWithExecutor() {
        System.err.println("The main Thread name is " + Thread.currentThread().getName());

        // 此处模拟开启一个异步任务,此处传入一个线程池
        WebAsyncTask<String> task1 = new WebAsyncTask<String>(10 * 1000L, asyncTestExecutor, () -> {
            System.err.println("The first Thread name is " + Thread.currentThread().getName());
            Thread.sleep(5000L);
            return "任务4顺利执行成功！任何异常都没有抛出！";
        });

        // 任务执行完成时调用该方法
        task1.onCompletion(() -> {
            System.err.println("任务4执行完成啦！");
        });

        System.err.println("task4继续处理其他事情！");
        return task1;
    }

    // 请求的主线程超时配置
}
